package com.wdemo.service.impl;

import cn.hutool.core.util.StrUtil;
import com.wdemo.config.AIProperties;
import com.wdemo.embedding.EmbeddingManager;
import com.wdemo.enums.ChatTypeEnum;
import com.wdemo.enums.EmbeddingTypeEnum;
import com.wdemo.memory.ChatMemoryManager;
import com.wdemo.pojo.domain.ChatSession;
import com.wdemo.pojo.vo.ChatPdfVo;
import com.wdemo.repository.FileRepository;
import com.wdemo.service.ChatPdfService;
import com.wdemo.service.ChatSessionService;
import com.wdemo.service.EmbeddingService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.regex.Pattern;

import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;

/**
 * @ClassName ChatPdfServiceImpl
 * @Description TODO
 * @Author WDEMO
 * @Date 2025/9/2 2:20
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatPdfServiceImpl implements ChatPdfService {

    private  EmbeddingService embeddingService;

    private final ChatSessionService chatSessionService;

    private final FileRepository fileRepository;

    private final ChatClient chatClient;

    private  VectorStore redisVectorStore;

    private final AIProperties aiProperties;

    private final ChatMemoryManager chatMemoryManager;

    private final EmbeddingManager embeddingManager;

    @PostConstruct
    public void init(){
        VectorStore vectorStore = embeddingManager.getVectorStore(EmbeddingTypeEnum.PDF);
        EmbeddingService embeddingService = new EmbeddingServiceImpl(vectorStore);
        this.embeddingService=embeddingService;
        this.redisVectorStore=vectorStore;
    }


    @Override
    public ChatPdfVo uploadPdf(MultipartFile file, String chatId) throws IOException {
        ChatSession chatSession = ChatSession.builder()
                .type("pdf")
                .title(StrUtil.sub(file.getOriginalFilename(), 0, 100))
                .updater(1L)
                .creater(1L)
                .userId(1L)
                .sessionId(chatId)
                .build();

        boolean save = chatSessionService.save(chatSession);

        Resource resource = file.getResource();

        fileRepository.save(chatId, resource);

        if (!save) {
            return null;
        }

        //添加到矢量库中
        embeddingService.saveVectorStore(resource);

        ChatPdfVo chatPdfVo = ChatPdfVo.builder()
                .chatId(chatId)
                .name(file.getOriginalFilename())
                .build();
        return chatPdfVo;
    }

    @Override
    public ChatPdfVo uploadPdfToAliyun(MultipartFile file, String chatId) throws IOException {
        ChatSession chatSession = ChatSession.builder()
                .type("pdf")
                .title(StrUtil.sub(file.getOriginalFilename(), 0, 100))
                .updater(1L)
                .creater(1L)
                .userId(1L)
                .sessionId(chatId)
                .build();

        boolean save = chatSessionService.save(chatSession);

        Resource resource = file.getResource();

        String path = fileRepository.saveByAliyun(chatId, file);

        if (!save) {
            return null;
        }

        //添加到矢量库中
        embeddingService.saveVectorStore(resource,path);

        ChatPdfVo chatPdfVo = ChatPdfVo.builder()
                .chatId(chatId)
                .name(file.getOriginalFilename())
                .build();
        return chatPdfVo;
    }

    @Override
    public ResponseEntity<Resource> downloadPdf(String chatId) {
        Resource resource = fileRepository.getFile(chatId);

        if (!(resource.exists())) {
            return ResponseEntity.notFound().build();
        }

        // 2.文件名编码，写入响应头
        String filename = URLEncoder.encode(Objects.requireNonNull(resource.getFilename()), StandardCharsets.UTF_8);
        // 安全地获取文件名
        if (filename == null || filename.isEmpty()) {
            filename = "download.pdf"; // 提供默认文件名
        }
        filename = URLEncoder.encode(filename, StandardCharsets.UTF_8);

        // 3.返回文件
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
                .body(resource);
    }

    @Override
    public ResponseEntity<Resource> downloadPdfByAliyun(String chatId) {
        Resource resource = fileRepository.getFileByAliyun(chatId);

        if (!(resource.exists())) {
            return ResponseEntity.notFound().build();
        }

        // 2.文件名编码，写入响应头
        String filename = URLEncoder.encode(Objects.requireNonNull(resource.getFilename()), StandardCharsets.UTF_8);
        // 安全地获取文件名
        if (filename == null || filename.isEmpty()) {
            filename = "download.pdf"; // 提供默认文件名
        }
        filename = URLEncoder.encode(filename, StandardCharsets.UTF_8);

        // 3.返回文件
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF)
                .header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
                .body(resource);
    }

    @Override
    public Flux<String> chat(String prompt, String chatId) {

        // 1.找到会话文件
        Resource file = fileRepository.getFileByAliyun(chatId);
        if (!file.exists()) {
            // 文件不存在，返回错误信息
            return Flux.just("错误：会话文件不存在！").concatWith(Flux.just("[End]"));

        }

        String filename = file.getFilename();
        if (filename == null || filename.isEmpty()) {
            return Flux.just("错误：文件名为空！").concatWith(Flux.just("[End]"));
        }
        FilterExpressionBuilder b = new FilterExpressionBuilder();
        SearchRequest searchRequest = SearchRequest.builder()
                .query(prompt)
                .similarityThreshold(0.7)
                .topK(99)
//                .filterExpression(b.in("file_name",filename).build())
                .build();

        //获取会话记忆管理器
        ChatMemory chatMemory = chatMemoryManager.getChatMemory(ChatTypeEnum.PDF);

        return chatClient.prompt()
                .system(aiProperties.getSystemPdfMessage().get())
                .user(prompt)
//                .advisors(a -> a.param(FILTER_EXPRESSION, "file_name == '" + file.getFilename() + "'"))
                .advisors(advisorSpec -> {
                    advisorSpec.param(CONVERSATION_ID,chatId);
                    advisorSpec.advisors(QuestionAnswerAdvisor.builder(redisVectorStore).searchRequest(searchRequest).build());
                    advisorSpec.advisors(MessageChatMemoryAdvisor.builder(chatMemory).build());
                })
                .stream()
                .content()
                .concatWith(Flux.just("[End]"));
    }

    /**
     * 转义Redis查询中的特殊字符
     * @param input 输入字符串
     * @return 转义后的字符串
     */
    private static final Pattern SPECIAL_CHARS = Pattern.compile("([\\+\\-\\&\\|\\!\\(\\)\\{\\}\\[\\]\\^\\\"\\~\\*\\?\\:\\\\])");
    public static String escapeForRedisQuery(String input) {
        if (input == null) {
            return "";
        }
        return SPECIAL_CHARS.matcher(input).replaceAll("\\\\$1");
    }
}
